package com.maddox.libnetwork;

import android.annotation.SuppressLint;
import android.text.TextUtils;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.arch.core.executor.ArchTaskExecutor;

import com.maddox.libnetwork.cache.CacheManager;

import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

/**
 * 网络请求封装
 * T:响应数据的实体类型
 * R:当前类的子类型
 */
public abstract class Request<T, R extends Request> implements Cloneable {
    //请求地址
    protected String mUrl;
    //请求头
    protected HashMap<String, String> headers = new HashMap<>();
    //请求参数
    protected HashMap<String, Object> params = new HashMap<>();
    //访问网络的方式,只访问本地缓存
    public static final int CACHE_ONLY = 1;
    //先访问缓存,在访问网络
    public static final int CACHE_FIRST = 2;
    //仅仅只访问网络,不进行缓存
    public static final int NET_ONLY = 3;
    //先访问网络,成功后缓存到本地
    public static final int NET_CACHE = 4;
    //缓存的key
    private String cacheKey;
    //响应结果的转换类型
    private Type mType;
    private Class mClzz;
    //默认的缓存策略
    private int mCacheStrategy = NET_ONLY;

    //枚举写法,方便使用
    @IntDef({CACHE_ONLY, CACHE_FIRST, NET_CACHE, NET_ONLY})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CacheStrategy {

    }

    //设置缓存模式
    public void setCacheStrategy(@CacheStrategy int cacheStrategy) {
        this.mCacheStrategy = cacheStrategy;
    }

    public Request(String url) {
        this.mUrl = url;
    }

    //添加请求头
    public R addHeader(String key, String value) {
        headers.put(key, value);
        return (R) this;
    }

    //添加请求参数
    public R addParam(String key, Object value) {
        if (value == null) {
            return (R) this;
        }
        //判断值的类型,如果属于八大基本类型,则加入
        try {
            Field field = value.getClass().getField("TYPE");
            Class claz = (Class) field.get(null);
            boolean primitive = claz.isPrimitive();
            if (primitive) {
                params.put(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (R) this;

    }

    //设置缓存key
    public R cacheKey(String key) {
        this.cacheKey = key;
        return (R) this;
    }

    //对于同步请求,因为没有JsonCallback回调,无法确定返回结果的类型,所以可以开放额外的设置
    public R responseType(Type type) {
        mType = type;
        return (R) this;
    }

    public R responseType(Class clzz) {
        mClzz = clzz;
        return (R) this;
    }

    //同步请求
    public ApiResponse<T> execute() {
        //当为只读取缓存时
        if (mCacheStrategy == CACHE_ONLY) {
            return readCache();
        }
       if(mCacheStrategy!=CACHE_ONLY){
           Call call = getCall();
           //正常网络请求
           ApiResponse<T> result = null;
           try {
               Response response = call.execute();
               result = parseResult(response, null);
           } catch (IOException e) {
               e.printStackTrace();
               if (result == null)
                   result = new ApiResponse<>();
               result.message = e.getMessage();

           }
           return result;
       }
       return null;
    }

    //读取缓存中的数据
    private ApiResponse<T> readCache() {
        String key = TextUtils.isEmpty(cacheKey) ? generateCacheKey() : cacheKey;
        ApiResponse<T> apiResponse = new ApiResponse<>();
        apiResponse.success = true;
        apiResponse.status = 304;
        apiResponse.message = "读取缓存成功";
        apiResponse.body = (T) CacheManager.getCache(key);
        return apiResponse;
    }

    //异步请求
    @SuppressLint("RestrictedApi")
    public void execute(final JsonCallback<T> jsonCallback) {

        //当为读取缓存模式
        if (mCacheStrategy != NET_ONLY) {
            //异步方式读取,并回调出去
            ArchTaskExecutor.getIOThreadExecutor().execute(() -> {
                ApiResponse<T> response = readCache();
                if (jsonCallback != null) {
                    jsonCallback.cacheSuccess(response);
                }
            });
        }
        //当不是只读缓存时还要请求网络获取数据
        if (mCacheStrategy != CACHE_ONLY) {
            //获取请求构建
            Call call = getCall();
            call.enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    //请求失败回调
                    ApiResponse<T> responseFailure = new ApiResponse<>();
                    responseFailure.message = e.getMessage();
                    jsonCallback.onFailure(responseFailure);
                }

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                    //请求成功分情况回调
                    ApiResponse<T> responseSuccess = parseResult(response, jsonCallback);
                    if (responseSuccess.success) {
                        jsonCallback.onSuccess(responseSuccess);
                    } else {
                        jsonCallback.onFailure(responseSuccess);
                    }

                }
            });

        }
    }

    //解析请求结果
    private ApiResponse<T> parseResult(Response response, JsonCallback<T> jsonCallback) {
        String message = null;
        int status = response.code();
        boolean success = response.isSuccessful();
        //创建返回结果
        ApiResponse<T> result = new ApiResponse<>();
        //创建转换器
        Convert convert = ApiService.sConvert;

        try {
            String content = response.body().string();
            if (success) {
                if (jsonCallback != null) {
                    //获取返回数据类型进行转换封装到响应结果中
                    //1.5增加泛型,但是有了类型擦除,除了接口的匿名内部类
                    ParameterizedType parameterizedType = (ParameterizedType) jsonCallback.getClass().getGenericSuperclass();
                    //获取接口声明的泛型的第一个类型参数T的类型
                    Type type = parameterizedType.getActualTypeArguments()[0];
                    //利用fastjson把实际的数据转化为对应类型的对象
                    result.body = (T) convert.convert(content, type);
                }else if (mType!=null){
                    result.body = (T) convert.convert(content, mType);
                }
            } else {
                message = content;
            }
        } catch (IOException e) {
            //出现异常
            result.message = e.getMessage();
            result.success = false;
        }

        //正常情况返回
        result.status = status;
        result.success = success;
        result.message = message;
        //当数据请求成功,并且设置为缓存请求模式时,进行数据缓存
        if (mCacheStrategy != NET_ONLY && result.success && result.body != null && result.body instanceof Serializable) {
            saveCache(result.body);

        }

        return result;
    }

    private void saveCache(T body) {
        String key = TextUtils.isEmpty(cacheKey) ? generateCacheKey() : cacheKey;
        CacheManager.save(key, body);
    }

    private String generateCacheKey() {
        //根据当前请求的url和请求参数生成一条缓存数据的key
        return UrlCreator.createUrlFromParams(mUrl, params);
    }

    /**
     * 封装构建请求的所有信息
     */
    private Call getCall() {
        okhttp3.Request.Builder builder = new okhttp3.Request.Builder();
        //添加头信息
        addHeaders(builder);
        //由子类去构建请求体
        okhttp3.Request request = generateRequest(builder);
        Call call = ApiService.okHttpClient.newCall(request);
        return call;
    }

    //由子类去实现get或者post请求
    protected abstract okhttp3.Request generateRequest(okhttp3.Request.Builder builder);

    //添加头信息
    private void addHeaders(okhttp3.Request.Builder builder) {
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }
    }

    @NonNull
    @Override
    public Request clone() throws CloneNotSupportedException {
        return (Request<T,R>) super.clone();
    }
}
